// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/audio/pulse/audio_manager_pulse.h"

#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/nix/xdg_util.h"
#include "base/stl_util.h"
#if defined(USE_ALSA)
#include "media/audio/alsa/audio_manager_alsa.h"
#endif
#include "media/audio/audio_device_description.h"
#include "media/audio/pulse/pulse_input.h"
#include "media/audio/pulse/pulse_output.h"
#include "media/audio/pulse/pulse_util.h"
#include "media/base/audio_parameters.h"
#include "media/base/channel_layout.h"

#if defined(DLOPEN_PULSEAUDIO)
#include "media/audio/pulse/pulse_stubs.h"

using media_audio_pulse::InitializeStubs;
using media_audio_pulse::kModulePulse;
using media_audio_pulse::StubPathMap;
#endif // defined(DLOPEN_PULSEAUDIO)

namespace media {

using pulse::AutoPulseLock;
using pulse::WaitForOperationCompletion;

// Maximum number of output streams that can be open simultaneously.
static const int kMaxOutputStreams = 50;

// Define bounds for the output buffer size.
static const int kMinimumOutputBufferSize = 512;
static const int kMaximumOutputBufferSize = 8192;

// Default input buffer size.
static const int kDefaultInputBufferSize = 1024;

#if defined(DLOPEN_PULSEAUDIO)
static const base::FilePath::CharType kPulseLib[] = FILE_PATH_LITERAL("libpulse.so.0");
#endif

AudioManagerPulse::AudioManagerPulse(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
    scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner,
    AudioLogFactory* audio_log_factory)
    : AudioManagerBase(std::move(task_runner),
        std::move(worker_task_runner),
        audio_log_factory)
    , input_mainloop_(NULL)
    , input_context_(NULL)
    , devices_(NULL)
    , native_input_sample_rate_(0)
    , native_channel_count_(0)
{
    SetMaxOutputStreamsAllowed(kMaxOutputStreams);
}

AudioManagerPulse::~AudioManagerPulse()
{
    Shutdown();
    // The Pulse objects are the last things to be destroyed since Shutdown()
    // needs them.
    DestroyPulse();
}

bool AudioManagerPulse::Init()
{
    // TODO(alokp): Investigate if InitPulse can happen on the audio thread.
    // It currently needs to happen on the main thread so that is InitPulse fails,
    // we can fallback to ALSA implementation. Initializing it on audio thread
    // would unblock the main thread and make InitPulse consistent with
    // DestroyPulse which happens on the audio thread.
    return InitPulse();
}

// Implementation of AudioManager.
bool AudioManagerPulse::HasAudioOutputDevices()
{
    AudioDeviceNames devices;
    GetAudioOutputDeviceNames(&devices);
    return !devices.empty();
}

bool AudioManagerPulse::HasAudioInputDevices()
{
    AudioDeviceNames devices;
    GetAudioInputDeviceNames(&devices);
    return !devices.empty();
}

void AudioManagerPulse::ShowAudioInputSettings()
{
#if defined(USE_ALSA)
    AudioManagerAlsa::ShowLinuxAudioInputSettings();
#endif
}

void AudioManagerPulse::GetAudioDeviceNames(
    bool input, media::AudioDeviceNames* device_names)
{
    DCHECK(device_names->empty());
    DCHECK(input_mainloop_);
    DCHECK(input_context_);
    AutoPulseLock auto_lock(input_mainloop_);
    devices_ = device_names;
    pa_operation* operation = NULL;
    if (input) {
        operation = pa_context_get_source_info_list(
            input_context_, InputDevicesInfoCallback, this);
    } else {
        operation = pa_context_get_sink_info_list(
            input_context_, OutputDevicesInfoCallback, this);
    }
    WaitForOperationCompletion(input_mainloop_, operation);

    // Prepend the default device if the list is not empty.
    if (!device_names->empty())
        device_names->push_front(AudioDeviceName::CreateDefault());
}

void AudioManagerPulse::GetAudioInputDeviceNames(
    AudioDeviceNames* device_names)
{
    GetAudioDeviceNames(true, device_names);
}

void AudioManagerPulse::GetAudioOutputDeviceNames(
    AudioDeviceNames* device_names)
{
    GetAudioDeviceNames(false, device_names);
}

AudioParameters AudioManagerPulse::GetInputStreamParameters(
    const std::string& device_id)
{
    int user_buffer_size = GetUserBufferSize();
    int buffer_size = user_buffer_size ? user_buffer_size : kDefaultInputBufferSize;

    // TODO(xians): add support for querying native channel layout for pulse.
    UpdateNativeAudioHardwareInfo();
    return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
        CHANNEL_LAYOUT_STEREO, native_input_sample_rate_, 16,
        buffer_size);
}

const char* AudioManagerPulse::GetName()
{
    return "PulseAudio";
}

AudioOutputStream* AudioManagerPulse::MakeLinearOutputStream(
    const AudioParameters& params,
    const LogCallback& log_callback)
{
    DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
    return MakeOutputStream(params, AudioDeviceDescription::kDefaultDeviceId);
}

AudioOutputStream* AudioManagerPulse::MakeLowLatencyOutputStream(
    const AudioParameters& params,
    const std::string& device_id,
    const LogCallback& log_callback)
{
    DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
    return MakeOutputStream(params, device_id.empty() ? AudioDeviceDescription::kDefaultDeviceId : device_id);
}

AudioInputStream* AudioManagerPulse::MakeLinearInputStream(
    const AudioParameters& params,
    const std::string& device_id,
    const LogCallback& log_callback)
{
    DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
    return MakeInputStream(params, device_id);
}

AudioInputStream* AudioManagerPulse::MakeLowLatencyInputStream(
    const AudioParameters& params,
    const std::string& device_id,
    const LogCallback& log_callback)
{
    DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
    return MakeInputStream(params, device_id);
}

AudioParameters AudioManagerPulse::GetPreferredOutputStreamParameters(
    const std::string& output_device_id,
    const AudioParameters& input_params)
{
    // TODO(tommi): Support |output_device_id|.
    VLOG_IF(0, !output_device_id.empty()) << "Not implemented!";

    int buffer_size = kMinimumOutputBufferSize;
    int bits_per_sample = 16;

    // Query native parameters where applicable; Pulse does not require these to
    // be respected though, so prefer the input parameters for channel count.
    UpdateNativeAudioHardwareInfo();
    int sample_rate = native_input_sample_rate_;
    ChannelLayout channel_layout = GuessChannelLayout(native_channel_count_);

    if (input_params.IsValid()) {
        bits_per_sample = input_params.bits_per_sample();
        channel_layout = input_params.channel_layout();
        buffer_size = std::min(kMaximumOutputBufferSize,
            std::max(buffer_size, input_params.frames_per_buffer()));
    }

    int user_buffer_size = GetUserBufferSize();
    if (user_buffer_size)
        buffer_size = user_buffer_size;

    return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
        sample_rate, bits_per_sample, buffer_size);
}

AudioOutputStream* AudioManagerPulse::MakeOutputStream(
    const AudioParameters& params,
    const std::string& device_id)
{
    DCHECK(!device_id.empty());
    return new PulseAudioOutputStream(params, device_id, this);
}

AudioInputStream* AudioManagerPulse::MakeInputStream(
    const AudioParameters& params, const std::string& device_id)
{
    return new PulseAudioInputStream(this, device_id, params,
        input_mainloop_, input_context_);
}

void AudioManagerPulse::UpdateNativeAudioHardwareInfo()
{
    DCHECK(input_mainloop_);
    DCHECK(input_context_);
    AutoPulseLock auto_lock(input_mainloop_);
    pa_operation* operation = pa_context_get_server_info(
        input_context_, AudioHardwareInfoCallback, this);
    WaitForOperationCompletion(input_mainloop_, operation);
}

bool AudioManagerPulse::InitPulse()
{
    DCHECK(!input_mainloop_);

#if defined(DLOPEN_PULSEAUDIO)
    StubPathMap paths;

    // Check if the pulse library is avialbale.
    paths[kModulePulse].push_back(kPulseLib);
    if (!InitializeStubs(paths)) {
        VLOG(1) << "Failed on loading the Pulse library and symbols";
        return false;
    }
#endif // defined(DLOPEN_PULSEAUDIO)

    // Create a mainloop API and connect to the default server.
    // The mainloop is the internal asynchronous API event loop.
    input_mainloop_ = pa_threaded_mainloop_new();
    if (!input_mainloop_)
        return false;

    // Start the threaded mainloop.
    if (pa_threaded_mainloop_start(input_mainloop_))
        return false;

    // Lock the event loop object, effectively blocking the event loop thread
    // from processing events. This is necessary.
    AutoPulseLock auto_lock(input_mainloop_);

    pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(input_mainloop_);
    input_context_ = pa_context_new(pa_mainloop_api, "Chrome input");
    if (!input_context_)
        return false;

    pa_context_set_state_callback(input_context_, &pulse::ContextStateCallback,
        input_mainloop_);
    if (pa_context_connect(input_context_, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL)) {
        VLOG(1) << "Failed to connect to the context.  Error: "
                << pa_strerror(pa_context_errno(input_context_));
        return false;
    }

    // Wait until |input_context_| is ready.  pa_threaded_mainloop_wait() must be
    // called after pa_context_get_state() in case the context is already ready,
    // otherwise pa_threaded_mainloop_wait() will hang indefinitely.
    while (true) {
        pa_context_state_t context_state = pa_context_get_state(input_context_);
        if (!PA_CONTEXT_IS_GOOD(context_state))
            return false;
        if (context_state == PA_CONTEXT_READY)
            break;
        pa_threaded_mainloop_wait(input_mainloop_);
    }

    return true;
}

void AudioManagerPulse::DestroyPulse()
{
    if (!input_mainloop_) {
        DCHECK(!input_context_);
        return;
    }

    {
        AutoPulseLock auto_lock(input_mainloop_);
        if (input_context_) {
            // Clear our state callback.
            pa_context_set_state_callback(input_context_, NULL, NULL);
            pa_context_disconnect(input_context_);
            pa_context_unref(input_context_);
            input_context_ = NULL;
        }
    }

    pa_threaded_mainloop_stop(input_mainloop_);
    pa_threaded_mainloop_free(input_mainloop_);
    input_mainloop_ = NULL;
}

void AudioManagerPulse::InputDevicesInfoCallback(pa_context* context,
    const pa_source_info* info,
    int error, void* user_data)
{
    AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);

    if (error) {
        // Signal the pulse object that it is done.
        pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
        return;
    }

    // Exclude the output devices.
    if (info->monitor_of_sink == PA_INVALID_INDEX) {
        manager->devices_->push_back(AudioDeviceName(info->description,
            info->name));
    }
}

void AudioManagerPulse::OutputDevicesInfoCallback(pa_context* context,
    const pa_sink_info* info,
    int error, void* user_data)
{
    AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);

    if (error) {
        // Signal the pulse object that it is done.
        pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
        return;
    }

    manager->devices_->push_back(AudioDeviceName(info->description,
        info->name));
}

void AudioManagerPulse::AudioHardwareInfoCallback(pa_context* context,
    const pa_server_info* info,
    void* user_data)
{
    AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);

    manager->native_input_sample_rate_ = info->sample_spec.rate;
    manager->native_channel_count_ = info->sample_spec.channels;
    pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
}

} // namespace media
